﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Linq.Dynamic;
using System.Reflection;
using System.Text;
using System.Xml.Linq;

namespace Dycox.ComponentModel
{
    public class ModelView : ViewGroup
    {
        public static readonly XNamespace ViewNamespace = "urn:effun-net-data-view";
        public static readonly XNamespace ModelNamespace = "urn:effun-net-dycox-data-model";

        private ModelView()
            : base("View")
        {
            
        }

        private ModelView(XElement element)
            : this()
        {
            string value;
            
            value = element.Element(ViewNamespace + "modelType").Value;
            Type type = Type.GetType(value);

            if (type == null)
                throw new InvalidOperationException("Model type not found while initializing view: " + value);

            _modelType = type;

            var da = (DisplayNameAttribute)Attribute.GetCustomAttribute(type, typeof(DisplayNameAttribute));
            if (da != null) _modelDisplayName = da.DisplayName;
            else _modelDisplayName = type.Name;

            ReadGroupItems(element.Element(ViewNamespace + "items"), this);
        }

        private static void ReadGroupItems(XElement parentElement, ViewGroup group)
        {
            
            if (parentElement != null)
            {
                foreach (var elt in parentElement.Elements())
                {
                    XName xname = elt.Name;

                    if (xname.Namespace == ViewNamespace)
                    {
                        string name = xname.LocalName;

                        if (name == "field")
                        {
                            var fld = new ViewField(elt);
                            group.Items.Add(fld);
                            fld.Initialize();
                        }
                        else if (name == "group")
                        {
                            ViewGroup g1 = new ViewGroup(elt);
                            group.Items.Add(g1);
                            ReadGroupItems(elt, g1);
                        }
                    }
                }
            }
        }

        public ModelView(Type modelType)
            : this()
        {
            if (modelType == null)
                throw new ArgumentNullException("modelType");

            _modelType = modelType;
            var da = (DisplayNameAttribute)Attribute.GetCustomAttribute(modelType, typeof(DisplayNameAttribute));
            if (da != null) _modelDisplayName = da.DisplayName;
            else _modelDisplayName = modelType.Name;

            foreach (PropertyDescriptor pd in TypeDescriptor.GetProperties(modelType, new Attribute[] { 
                BrowsableAttribute.Yes, VisibleAttribute.Yes}))
            {
                Items.Add(new ViewField(pd));
            }
        }

        private ModelView _parentView;

        public ModelView ParentView
        {
            get { return _parentView; }
        }

        public bool IsSubView
        {
            get { return _parentView != null; }
        }

        public ModelView CreateSubView(object filter)
        {
            return CreateSubView(filter, true);
        }

        public ModelView CreateSubView(object filter, bool keepStructure)
        {
            ModelView view = new ModelView();

            view._modelDisplayName = _modelDisplayName;
            view._modelType = _modelType;
            view._parentView = this;

            if (keepStructure)
            {
                WalkFields(this, (f, s) =>
                    {
                        var f1 = f.GetMatchedField(filter);
                        if (f1 != null)
                            ((Collection)s).Add(f1);
                    }, view.Items);
            }
            else
                AddFieldsToSubView(this, view, filter);


            return view;
        }

        private void AddFieldsToSubView(ViewGroup thisGroup, ViewGroup subGroup, object filter)
        {

            foreach (var item in thisGroup.Items)
            {
                if (item is ViewField)
                {
                    var f1 = ((ViewField)item).GetMatchedField(filter);
                    if (f1 != null)
                        subGroup.Items.Add(f1);
                }
                else
                {
                    ViewGroup g0 = (ViewGroup)item;
                    ViewGroup g1 = new ViewGroup(g0);

                    AddFieldsToSubView(g0, g1, filter);

                    if (g1.Items.Count > 0)
                        subGroup.Items.Add(g1);

                }
            }
        }

        public ViewField[] MatchFields(object filter)
        {
            List<ViewField> list = new List<ViewField>();

            WalkFields((fld, state) =>
                {
                    var f = fld.GetMatchedField(filter);
                    if (f != null) list.Add(f);
                }, filter);

            return list.ToArray();
        }

        private Type _modelType;

        public Type ModelType
        {
            get { return _modelType; }
        }

        private string _modelDisplayName;

        public string ModelDisplayName
        {
            get { return _modelDisplayName; }
            set { _modelDisplayName = value; }
        }


        public static ModelView LoadView(Type type)
        {
            string name = type.FullName;
            name += ".View.xml";
            return LoadView(type.Assembly, name);
        }

        public static ModelView LoadView(System.Reflection.Assembly assembly, string resName)
        {
            ModelView vs;

            resName = assembly.GetManifestResourceNames().SingleOrDefault(o => string.Compare(o, resName, true) == 0);

            if (resName == null)
                return null;

            Stream stream;

            stream = assembly.GetManifestResourceStream(resName);
            using (stream)
            {
                vs = LoadView(stream);
            }

            return vs;
        }

        public static ModelView LoadView(Stream input)
        {
            XDocument doc = XDocument.Load(input);

            ModelView settings;

            settings = new ModelView(doc.Root);

            return settings;
        }

        public object[] Transform(IEnumerable source)
        {
            return Transform(source, this);
        }

        public object[] Transform(IEnumerable source, object filter)
        {
            return Transform(source, this.CreateSubView(filter, false));
        }

        private static object[] Transform(IEnumerable source, ModelView view)
        {
            if (source == null)
                throw new ArgumentNullException("source");

            var fields = view.GetFields(true);
            List<PropertyState> states = new List<PropertyState>(fields.Length);
            foreach (var vf in fields)
            {
                if (vf.Property != null)
                {
                    states.Add(new PropertyState(vf));
                }
            }

            Type type = DynamicExpression.CreateClass(states);
            ArrayList list = new ArrayList();

            foreach (object src in source)
            {
                object obj;
                obj = Activator.CreateInstance(type);

                foreach (var state in states)
                {
                    object value;

                    value = Utils.DataBinderUtil.Eval(src, state.ViewField.PropertyName);

                    state.GetPropertyInfo(type).SetValue(obj, value);
                }

                list.Add(obj);
            }

            return (object[])list.ToArray(type);
        }

        class PropertyState : DynamicProperty
        {
            public PropertyState(ViewField vf)
                : base(vf.Name.Replace(Type.Delimiter, '_'), vf.Property.PropertyType)
            {
                this.ViewField = vf;
            }

            public ViewField ViewField { get; private set; }

            PropertyInfo _propertyInfo;

            public PropertyInfo GetPropertyInfo(Type type)
            {
                if (_propertyInfo == null)
                    _propertyInfo = type.GetProperty(this.Name, BindingFlags.Public | BindingFlags.Instance);

                return _propertyInfo;
            }
        }

        //class ViewFieldEnumerator : IEnumerator<ViewField>
        //{
        //    ModelView _view;
        //    ViewGroup _currentGroup;
        //    int _fieldIndex;

        //    public ViewFieldEnumerator(ModelView view)
        //    {
        //        if (view == null)
        //            throw new ArgumentNullException("view");

        //        _view = view;
        //        Reset();
        //    }

        //    private void EnterGroup(ViewGroup group)
        //    {
        //        _currentGroup = group;
        //    }

        //    private ViewGroup ExitGroup()
        //    {
        //        _currentGroup = _currentGroup.Parent;
        //        return _currentGroup;
        //    }

        //    public ViewGroup CurrentGroup
        //    {
        //        get
        //        {
        //            return _currentGroup;
        //        }
        //    }

        //    public ViewField Current
        //    {
        //        get
        //        {
        //            var g = CurrentGroup;
        //            if (g == null || _fieldIndex < 0 || _fieldIndex >= g.Fields.Count)
        //                throw new InvalidOperationException();

        //            return g.Fields[_fieldIndex];
        //        }
        //    }

        //    public void Dispose()
        //    {
        //    }

        //    object IEnumerator.Current
        //    {
        //        get { return this.Current; }
        //    }

        //    public bool MoveNext()
        //    {

        //        var group = CurrentGroup;

        //        if (group == null)
        //        {
        //            EnterGroup(group = _view);
        //        }

        //        if (group.HasFields)
        //        {
        //            if (++_fieldIndex < group.Fields.Count)
        //            {
        //                return true;
        //            }
        //        }

        //        var parent = ExitGroup();

        //        if (parent != null)
        //        {
        //            var coll = parent.Groups;
        //            int index = coll.IndexOf(group) + 1;
        //            if (index < coll.Count)
        //            {
        //                var g = coll[index];

        //                if (g.HasFields)
        //                {
        //                    EnterGroup(g);
        //                    return MoveNext();
        //                }
        //            }
        //        }

        //        return false;
        //    }

        //    public void Reset()
        //    {
        //        _fieldIndex = -1;
        //        _currentGroup = null;
        //    }
        //}


    }
}
